﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ArtMath.Core.Data;

namespace ArtMath.PointCollection
{
    /// <summary>
    /// 带长度计算的曲线
    /// </summary>
    [Serializable]
    public class LengthCurve : Curve
    {
        List<double> prefixLength;
        public double Length
        {
            get
            {
                double len = GetLength(0, samples.Count - 1);
                if (IsClosed)
                {
                    len += ConnectionLength;
                }
                return len;
            }
        }
        public double GetLength(int startIndex, int endIndex)
        {
            if (prefixLength == null) throw new NotImplementedException();
            if (startIndex > endIndex) throw new ArgumentException("The StartIndex cannot be greater than the EndIndex");
            if (startIndex == endIndex) return 0;
            return prefixLength[endIndex] - prefixLength[startIndex];
        }
        public double GetToEndLength(int startIndex)
        {
            return GetLength(startIndex, samples.Count - 1);
        }
        public double GetFromStartLength(int endIndex)
        {
            return GetLength(0, endIndex);
        }
        public override void Add(PointD item)
        {
            base.Add(item);
            if (prefixLength == null)
            {
                prefixLength = new List<double>();
            }
            if (samples.Count == 1)
            {
                prefixLength.Add(0);
            }
            else
            {
                prefixLength.Add(prefixLength[prefixLength.Count - 1] + item.DistanceTo(samples[samples.Count - 2]));
            }
        }
        public override void Insert(int index, PointD item)
        {
            base.Insert(index, item);
            if (prefixLength == null)
            {
                prefixLength = new List<double>();
            }
            if (samples.Count == 1)
            {
                prefixLength.Add(0);
            }
            else
            {
                prefixLength.Insert(index, prefixLength[index - 1] + item.DistanceTo(samples[index - 1]));
                refreshLength(index + 1);
            }
        }
        void refreshLength(int startIndex)
        {
            for (int i = startIndex; i < prefixLength.Count; i++)
            {
                prefixLength[i] = prefixLength[i - 1] + samples[i].DistanceTo(samples[i - 1]);
            }
        }
        public override void RemoveAt(int index)
        {
            base.RemoveAt(index);
            if (prefixLength != null)
            {
                prefixLength.RemoveAt(index);
                refreshLength(index);
            }
        }
        public override bool Remove(PointD item)
        {
            int index = samples.IndexOf(item);
            bool success = base.Remove(item);
            if (success && prefixLength != null)
            {
                prefixLength.RemoveAt(index);
                refreshLength(index);
            }
            return success;
        }
        public override void Clear()
        {
            base.Clear();
            if (prefixLength != null)
            {
                prefixLength.Clear();
            }
        }
        public PointD SlideDestination(PointD currentLocation, double length, out bool reverse)
        {
            reverse = false;
            if (length == 0) return currentLocation;
            int index = 0;
            LineSegmentIterator((PointD pt1, PointD pt2) =>
            {
                if (currentLocation.OnLineSegment(pt1, pt2))
                {
                    return false;
                }
                index++;
                return true;
            });
            length += currentLocation.DistanceTo(samples[index]);
            return SlideDestination(index, length, out reverse);
        }
        //double excess;
        //public double ExcessLength
        //{
        //    get { return excess; }
        //}
        public PointD SlideDestination(int currentIndex, double length, out bool reverse)
        {
            reverse = false;
            if (length == 0)
            {
                return samples[currentIndex];
            }
            double toEnd = GetLength(currentIndex, samples.Count - 1), toStart = GetLength(0, currentIndex), excess;
            if (IsClosed)
            {
                length %= Length;
                //excess = double.NaN;
                toEnd += ConnectionLength;
                if (length > 0)
                {
                    if (length <= toEnd)
                    {
                        return binaryFindDestination(currentIndex, samples.Count, length);
                    }
                    else
                    {
                        return binaryFindDestination(0, currentIndex, length - toEnd);
                    }
                }
                else
                {
                    if (-length <= toStart)
                    {
                        return binaryFindDestination(0, currentIndex, length + toStart);
                    }
                    else
                    {
                        return binaryFindDestination(currentIndex, samples.Count, Length + length);
                    }
                }
            }
            else
            {
                length %= 2 * Length;
                if (length > 0)
                {
                    excess = length - toEnd;
                    if (excess <= 0)
                    {
                        return binaryFindDestination(currentIndex, samples.Count - 1, length);
                    }
                    else
                    {
                        if (excess <= Length)
                        {
                            reverse = true;
                            return binaryFindDestination(0, samples.Count - 1, Length - excess);
                        }
                        else
                        {
                            return binaryFindDestination(0, currentIndex, excess - Length);
                        }
                    }
                }
                else
                {
                    excess = -length - toStart;
                    if (excess <= 0)
                    {
                        return binaryFindDestination(0, currentIndex, -excess);
                    }
                    else
                    {
                        if (excess <= Length)
                        {
                            reverse = true;
                            return binaryFindDestination(0, samples.Count - 1, excess);
                        }
                        else
                        {
                            return binaryFindDestination(currentIndex, samples.Count - 1, toEnd - excess + Length);
                        }
                    }
                }
            }
        }
        PointD binaryFindDestination(int low, int high, double len)
        {
            int startIndex = low;
            while (low < high)
            {
                int mid = low + ((high - low) >> 1);
                if (GetLength(startIndex, mid) >= len)
                {
                    high = mid;
                }
                else
                {
                    low = mid + 1;
                }
            }
            if (IsClosed && high == samples.Count)
            {
                len = GetLength(startIndex, samples.Count - 1) + ConnectionLength - len;
            }
            else
            {
                len = GetLength(startIndex, high) - len;
            }
            PointD res = samples[high % samples.Count];
            if (len == 0)
            {
                return res;
            }
            return res.Offset(res.GetAngle(samples[high - 1]), len);
        }
    }
}
